
declare global {
  interface Number {
    clamp(min: number, max: number): number
  }
  interface Array<T> {
    forof(callValue: (t: T) => boolean): T
    toFloat32Array(): Float32Array
    vClone(): number[]
    vIsEqual(v: number[]): boolean
    vNormalize(): number[]
    vDistance(v: number[]): number
    vDistanceSq(v: number[]): number
    vDot(v: number[]): number
    vLength(): number
    vLengthSq(): number
    vAdd(v: number[]): number[]
    vAddNew(v: number[]): number[]
    vSub(v: number[]): number[]
    vSubNew(v: number[]): number[]
    vNegate(): number[]
    vScale(n: number): number[]
    vScaleNew(n: number): number[]
    vEqualTo(v: number[]): boolean
    vMultiply(v: number[]): number[]
    vMultiplyNew(v: number[]): number[]
    vMin(v: number[]): number[]
    vMinNew(v: number[]): number[]
    vMax(v: number[]): number[]
    vMaxNew(v: number[]): number[]
    //
    v2Angle(): number
    v2Cross(v: number[]): number
    v2ClosestPtTo(v: number[]): number
    v2Rotate(v: number): number[]
    v3Cross(v: number[], vNew?: number[]): number[]
    v3Angle(v: number[]): number
    v3ApplyQua(q: number[]): number[]
    qNormalize(): number[]
    qConjugate(): number[]
    qMultiplyNew(q: number[]): number[]
    qFromV3Tov3(v0: number[], v1: number[]): number[]
    qFromAxisAngle(axis: number[], angle: number): number[]
    mSet(i: number, j: number, value: number): number[][]
    indexElements(): any

    toV2_yi(): Array<T>
    toV2a_yi(): Array<T>

    vec3ToArr(closed?: boolean): number[] | number[][]
    vecToArr(closed?: boolean): number[] | number[][]
    vecToArr2(closed?: boolean, precision?: number): number[][] | number[][][]
    vecToArr3Y(closed?: boolean): number[] | number[][]
    vecToArr3Z(closed?: boolean): number[] | number[][]
  }
  interface Float32Array {
    toArray(): number[]
  }
  interface Uint32Array {
    toArray(): number[]
    toArray_vtkPoly(): number[]
  }
}
Float32Array.prototype.toArray = function () {
  const nums: number[] = []
  for (const n of this) {
    nums.push(n)
  }
  return nums
}

Uint32Array.prototype.toArray = function () {
  const nums: number[] = []
  for (const n of this) {
    nums.push(n)
  }
  return nums
}
Uint32Array.prototype.toArray_vtkPoly = function () {
  const nums: number[] = []
  for (let i = 0, l = this.length; i < l; i += 4) {
    nums.push(this[i + 1], this[i + 2], this[i + 3])
  }
  return nums
}
Array.prototype.forof = function (callValue: (t: any) => boolean): any {
  for (const v of this) {
    if (callValue(v)) return v
  }
}

Array.prototype.vClone = function () {
  return vClone(this)
}
Array.prototype.vIsEqual = function (v: number[]) {
  return vIsEqual(this, v)
}
Array.prototype.vNormalize = function () {
  return vNormalize(this)
}
Array.prototype.vDistance = function (v: number[]) {
  return vDistance(this, v)
}
Array.prototype.vDistanceSq = function (v: number[]) {
  return vDistanceSq(this, v)
}
Array.prototype.vDot = function (v: number[]) {
  return vDot(this, v)
}
Array.prototype.vSub = function (v: number[]) {
  return vSub(this, v)
}
Array.prototype.vSubNew = function (v: number[]) {
  return vSubNew(this, v)
}
Array.prototype.vAdd = function (v: number[]) {
  return vAdd(this, v)
}
Array.prototype.vAddNew = function (v: number[]) {
  return vAddNew(this, v)
}
Array.prototype.vNegate = function () {
  return vNegate(this)
}
Array.prototype.vScale = function (n: number) {
  return vScale(this, n)
}
Array.prototype.vScaleNew = function (n: number) {
  return vScaleNew(this, n)
}
Array.prototype.vEqualTo = function (n: number[]) {
  return vEqualTo(this, n)
}
Array.prototype.vMultiply = function (v: number[]) {
  return vMultiply(this, v)
}
Array.prototype.vMultiplyNew = function (v: number[]) {
  return vMultiplyNew(this, v)
}
Array.prototype.vMin = function (v: number[]) {
  return vMultiplyNew(this, v)
}
Array.prototype.vMin = function (v: number[]) {
  return vMin(this, v)
}
Array.prototype.vMinNew = function (v: number[]) {
  return vMinNew(this, v)
}
Array.prototype.vMax = function (v: number[]) {
  return vMax(this, v)
}
Array.prototype.vMaxNew = function (v: number[]) {
  return vMaxNew(this, v)
}

Array.prototype.v2Angle = function () {
  return v2Angle(this)
}
Array.prototype.v2ClosestPtTo = function (v: number[]) {
  return v2ClosestPtTo(this, v)
}
Array.prototype.v2Cross = function (v: number[]) {
  return v2Cross(this, v)
}

Array.prototype.v2Rotate = function (v: number) {
  return v2Rotate(this, v)
}

Array.prototype.v3Cross = function (v: number[], vNew?: number[]) {
  return v3Cross(this, v, vNew)
}
Array.prototype.v3Angle = function (v: number[]) {
  return v3Angle(this, v)
}
Array.prototype.vLength = function () {
  return vLength(this)
}
Array.prototype.vLengthSq = function () {
  return vLengthSq(this)
}
Array.prototype.v3ApplyQua = function (q: number[]) {
  return v3ApplyQua(this, q)
}
Array.prototype.qNormalize = function () {
  return qNormalize(this)
}
Array.prototype.qConjugate = function () {
  return qConjugate(this)
}
Array.prototype.qMultiplyNew = function (q: number[]) {
  return qMultiplyNew(this, q)
}
Array.prototype.qFromV3Tov3 = function (v0: number[], v1: number[]) {
  return qFromV3Tov3(v0, v1)
}
Array.prototype.qFromAxisAngle = function (axis: number[], angle: number) {
  return qFromAxisAngle(axis, angle)
}
Array.prototype.toV2_yi = function () {
  toV2_yi(this)
  return this
}
Array.prototype.toV2a_yi = function () {
  toV2a_yi(this)
  return this
}
Array.prototype.mSet = function (i: number, j: number, value: number): number[][] {
  return mSet(this, i, j, value)
}
Array.prototype.indexElements = function (): number[][] {
  return indexElements(this)
}
Array.prototype.vec3ToArr = function (closed?: boolean): number[] | number[][] {
  return vec3ToArr(this, closed)
}
Array.prototype.vecToArr = function (closed?: boolean): number[] | number[][] {
  return vecToArr(this, closed)
}
Array.prototype.vecToArr2 = function (closed?: boolean, precision?: number): number[][] | number[][][] {
  return vecToArr2(this, closed, precision)
}

Array.prototype.vecToArr3Y = function (closed?: boolean): number[] | number[][] {
  return vecToArr3Y(this, closed)
}
Array.prototype.vecToArr3Z = function (closed?: boolean): number[] | number[][] {
  return vecToArr3Z(this, closed)
}

function toA3Arr(data: Float32Array) {
  const a: number[][] = []
  const len = data.length
  for (let i = 0; i < len; i += 3) {
    a.push([data[i], data[i + 1], data[i + 2]])
  }
  return a
}

function forDo(v: number[], callback: (i: number) => void) {
  for (let i = 0, len = v.length; i < len; i++) {
    callback(i)
  }
}
function vClone(v: number[]) {
  return [...v]
}
function vIsEqual(v0: number[], v1: number[]) {
  for (let i = 0, len = v0.length; i < len; i++) {
    if (v0[i] !== v1[i]) return false
  }
  return true
}
function vLength(v: number[]) {
  let sq = 0
  forDo(v, (i) => sq += v[i] * v[i])
  return Math.sqrt(sq)
}
function vLengthSq(v: number[]) {
  let sq = 0
  forDo(v, (i) => sq += v[i] * v[i])
  return sq
}
function vNormalize(v: number[]) {
  const len = vLength(v) || 1
  forDo(v, (i) => v[i] *= 1 / len)
  return v
}
function vDistance(v0: number[], v1: number[]) {
  return vLength(vSubNew(v0, v1))
}
function vDistanceSq(v0: number[], v1: number[]) {
  return vLengthSq(vSubNew(v0, v1))
}
function vDot(v0: number[], v1: number[]) {
  let d = 0
  forDo(v0, (i) => d += v0[i] * v1[i])
  return d
}
function vAdd(v0: number[], v1: number[]) {
  forDo(v0, (i) => v0[i] += v1[i])
  return v0
}
function vAddNew(v0: number[], v1: number[]) {
  const rs: number[] = []
  forDo(v0, (i) => rs.push(v0[i] + v1[i]))
  return rs
}
function vSub(v0: number[], v1: number[]) {
  forDo(v0, (i) => v0[i] -= v1[i])
  return v0
}
function vSubNew(v0: number[], v1: number[]) {
  const rs: number[] = []
  forDo(v0, (i) => rs.push(v0[i] - v1[i]))
  return rs
}
function vNegate(v: number[]) {
  return vScale(v, -1)
}
function vScale(v: number[], n: number) {
  forDo(v, (i) => v[i] *= n)
  return v
}
function vScaleNew(v: number[], n: number) {
  const rs: number[] = []
  forDo(v, (i) => rs.push(v[i] * n))
  return rs
}

function vEqualTo(v0: number[], v1: number[]) {
  for (let i = 0, len = v0.length; i < len; i++) {
    const c = Math.abs(v0[i] - v1[i])
    if (c > 0.000001) return false
  }
  return true
}

function v2Angle(v: number[]) {
  let angle = Math.atan2(v[1], v[0])
  if (angle < 0) angle = 2 * Math.PI + angle
  return angle
}

function v2ClosestPtTo(v0: number[], v1: number[]) {
  let dx = v0[0] - v1[0];
  let dy = v0[1] - v1[1];
  return Math.sqrt(dx * dx + dy * dy)
}

function v2Cross(v0: number[], v1: number[]) {
  return v0[0] * v1[1] - v0[1] * v1[0]
}

function vMultiply(v0: number[], v1: number[]) {
  forDo(v0, (i) => v0[i] *= v1[i])
  return v0
}

function vMultiplyNew(v0: number[], v1: number[]) {
  const v: number[] = []
  forDo(v0, (i) => v[i] = v0[i] * v1[i])
  return v
}

function vMin(v0: number[], v1: number[]) {
  forDo(v0, (i) => v0[i] = Math.min(v0[i], v1[i]))
  return v0
}
function vMinNew(v0: number[], v1: number[]) {
  const v: number[] = []
  forDo(v0, (i) => v[i] = Math.min(v0[i], v1[i]))
  return v
}

function vMax(v0: number[], v1: number[]) {
  forDo(v0, (i) => v0[i] = Math.max(v0[i], v1[i]))
  return v0
}
function vMaxNew(v0: number[], v1: number[]) {
  const v: number[] = []
  forDo(v0, (i) => v[i] = Math.max(v0[i], v1[i]))
  return v
}

function v2Rotate(v: number[], a: number) {
  const c = Math.cos(a), s = Math.sin(a);
  const x = v[0] * c - v[1] * s
  const y = v[0] * s + v[1] * c
  v[0] = x, v[1] = y
  return v
}

function v3Cross(v0: number[], v1: number[], v2?: number[]) {
  v2 = v2 || ([] as number[])
  v2[0] = v0[1] * v1[2] - v0[2] * v1[1];
  v2[1] = v0[2] * v1[0] - v0[0] * v1[2];
  v2[2] = v0[0] * v1[1] - v0[1] * v1[0];
  return v2
}
function v3Angle(v0: number[], v1: number[]) {
  const denominator = Math.sqrt(vLengthSq(v0) * vLengthSq(v1))
  if (denominator === 0) return Math.PI / 2
  const theta = vDot(v0, v1) / denominator
  return Math.acos(Math.min(Math.max(theta, - 1), 1))
}
function v3ApplyQua(v: number[], q: number[]) {
  const x = v[0], y = v[1], z = v[2];
  const qx = q[0], qy = q[1], qz = q[2], qw = q[3];
  // calculate quat * vector
  const ix = qw * x + qy * z - qz * y;
  const iy = qw * y + qz * x - qx * z;
  const iz = qw * z + qx * y - qy * x;
  const iw = - qx * x - qy * y - qz * z;
  // calculate result * inverse quat
  const _v: number[] = []
  _v[0] = ix * qw + iw * - qx + iy * - qz - iz * - qy;
  _v[1] = iy * qw + iw * - qy + iz * - qx - ix * - qz;
  _v[2] = iz * qw + iw * - qz + ix * - qy - iy * - qx;
  return _v
}
function qFromV3Tov3(v0: number[], v1: number[]) {
  let r = vDot(v0, v1) + 1;
  const qua: number[] = []
  if (r < Number.EPSILON) {
    r = 0;
    if (Math.abs(v0[0]) > Math.abs(v0[2])) {
      qua[0] = - v0[1];
      qua[1] = v0[0];
      qua[2] = 0;
      qua[3] = r;
    } else {
      qua[0] = 0;
      qua[1] = - v0[2];
      qua[2] = v0[1];
      qua[3] = r;
    }
  } else {
    qua[0] = v0[1] * v1[2] - v0[2] * v1[1];
    qua[1] = v0[2] * v1[0] - v0[0] * v1[2];
    qua[2] = v0[0] * v1[1] - v0[1] * v1[0];
    qua[3] = r;
  }
  return qNormalize(qua)
}
function qNormalize(q: number[]) {
  let l = vLength(q);
  if (l === 0) {
    q[0] = 0;
    q[1] = 0;
    q[2] = 0;
    q[3] = 1;
  } else {
    l = 1 / l;
    q[0] = q[0] * l;
    q[1] = q[1] * l;
    q[2] = q[2] * l;
    q[3] = q[3] * l;
  }
  return q
}
function qConjugate(q: number[]) {
  q[0] *= - 1
  q[1] *= - 1
  q[2] *= - 1
  return q
}
function qMultiplyNew(a: number[], b: number[]) {
  const qax = a[0], qay = a[1], qaz = a[2], qaw = a[3];
  const qbx = b[0], qby = b[1], qbz = b[2], qbw = b[3];

  const q: number[] = []
  q[0] = qax * qbw + qaw * qbx + qay * qbz - qaz * qby;
  q[1] = qay * qbw + qaw * qby + qaz * qbx - qax * qbz;
  q[2] = qaz * qbw + qaw * qbz + qax * qby - qay * qbx;
  q[3] = qaw * qbw - qax * qbx - qay * qby - qaz * qbz;
  return q
}
function qFromAxisAngle(axis: number[], angle: number) {
  const halfAngle = angle / 2, s = Math.sin(halfAngle);
  const qua: number[] = []
  qua[0] = axis[0] * s;
  qua[1] = axis[1] * s;
  qua[2] = axis[2] * s;
  qua[3] = Math.cos(halfAngle);
  return qua
}

function mSet(m: number[][], i: number, j: number, value: number) {
  let vi = m[i]
  if (!vi) vi = m[i] = []
  vi[j] = value
  return m
}

function indexElements(elementList: any[]) {
  let i = 0, index: any = {};
  for (let element of elementList) {
    index[element] = i++;
  }
  return index;
}

function toV2a_yi(v2s: any[]) {
  for (const v2 of v2s) {
    for (const _v2 of v2) _v2.y = -_v2.y
  }
}

function toV2_yi(v2s: any[]) {
  for (const v2 of v2s) {
    v2.y = -v2.y
  }
}

function vecToArr2(v2s: any[], closed?: boolean, _precision?: number) {
  if (!v2s.length) return []
  if (Array.isArray(v2s[0])) {
    const arrs: number[][][] = []
    for (const vs of v2s) {
      const arr = toGet(vs)
      if (arr.length) arrs.push(arr)
    }
    return arrs
  } else return toGet(v2s)
  function toGet(vs: any[]) {
    const arr2: number[][] = []
    for (let v of vs) {
      const va2 = v.z === undefined ?
        [v.x, v.y] :
        [v.x, v.y, v.z]
      arr2.push(va2)
    }
    if (closed && vs.length > 0) {
      const v = vs[0]
      const va2 = v.z === undefined ?
        [v.x, v.y] :
        [v.x, v.y, v.z]
      arr2.push(va2)
    }
    return arr2
  }

}

function vec3ToArr(arr: any[], closed?: boolean) {
  if (!arr.length) return []
  if (Array.isArray(arr[0])) {
    const arrs: number[][] = []
    for (const vs of arr) {
      const _pts = toGet(vs)
      if (_pts.length) arrs.push(_pts)
    }
    return arrs
  } else return toGet(arr)
  function toGet(vs: any[]) {
    const arr: number[] = []
    for (let v of vs) {
      arr.push(v.x, v.y, v.z)
    }
    if (closed && arr.length > 0) {
      arr.push(arr[0], arr[1], arr[2])
    }
    return arr
  }
}
function vecToArr(arr: any[], closed?: boolean) {
  if (!arr.length) return []
  if (Array.isArray(arr[0])) {
    const arrs: number[][] = []
    for (const vs of arr) {
      const _pts = toGet(vs)
      if (_pts.length) arrs.push(_pts)
    }
    return arrs
  } else return toGet(arr)
  function toGet(vs: any[]) {
    const arr: number[] = []
    for (let v of vs) {
      arr.push(v.x, v.y)
      if (v.z) arr.push(v.z)
    }
    if (closed && vs.length > 0) {
      const v = vs[0]
      arr.push(v.x, v.y)
      if (v.z) arr.push(v.z)
    }
    return arr
  }
}
function vecToArr3Y(arr: any[], closed?: boolean) {
  if (!arr.length) return []
  if (Array.isArray(arr[0])) {
    const arrs: number[][] = []
    for (const vs of arr) {
      const _pts = toGet(vs)
      if (_pts.length) arrs.push(_pts)
    }
    return arrs
  } else return toGet(arr)
  function toGet(vs: any[]) {
    const arr: number[] = []
    for (let v of vs) {
      arr.push(v.x, 0, v.y)
    }
    if (closed && arr.length > 0) {
      arr.push(arr[0], arr[1], arr[2])
    }
    return arr
  }
}
function vecToArr3Z(arr: any[], closed?: boolean) {
  if (!arr.length) return []
  if (Array.isArray(arr[0])) {
    const arrs: number[][] = []
    for (const vs of arr) {
      const _pts = toGet(vs)
      if (_pts.length) arrs.push(_pts)
    }
    return arrs
  } else return toGet(arr)
  function toGet(vs: any[]) {
    const arr: number[] = []
    for (let v of vs) {
      arr.push(v.x, v.y, 0)
    }
    if (closed && arr.length > 0) {
      arr.push(arr[0], arr[1], arr[2])
    }
    return arr
  }
}
function arrMath() { }
export default arrMath
export { toV2_yi, toV2a_yi, toA3Arr, indexElements, vec3ToArr, vecToArr, vecToArr2, vecToArr3Y, vecToArr3Z }
export {
  vClone, vIsEqual, vLength, vLengthSq, vNormalize, vDistance, vDistanceSq, vDot,
  vAdd, vAddNew, vSubNew, vSub,
  vNegate, vScaleNew, vEqualTo, vScale, v2Angle, v2ClosestPtTo, v2Cross, vMultiply, vMultiplyNew, v2Rotate, v3Cross, v3Angle,
  qFromV3Tov3, qNormalize, qConjugate, v3ApplyQua, qMultiplyNew, qFromAxisAngle,
  mSet
}